﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ThePresenter.Net.PresenterCom.Uids;

namespace ThePresenter.Net.NetCom
{
	public class ClientManager
	{
		#region Fields

		/// <summary>
		/// Internal list of clients.
		/// </summary>
		private Dictionary<Uid, Client> _Clients;

		#endregion

		#region Properties

		/// <summary>
		/// Gets the count of clients.
		/// </summary>
		/// <value>The count.</value>
		public int Count
		{
			get { return this._Clients.Count; }
		}

		#endregion

		#region Events
		/// <summary>
		/// Occurs when one of clients disconnects.
		/// </summary>
		public event Client.ClientDisconnectedEventHandler OnClientDisconnected;

		/// <param name="ClientUid">Uid of removed client.</param>
		public delegate void ClientRemovedEventHandler(Uid ClientUid);

		/// <summary>
		/// Occurs when Client is removed.
		/// </summary>
		public event ClientRemovedEventHandler OnClientRemoved;
		#endregion

		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="ClientManager"/> class.
		/// </summary>
		public ClientManager()
		{
			// Initialize internal list of clients
			this._Clients = new Dictionary<Uid, Client>();
		}

		#endregion

		#region Methods

		/// <summary>
		/// Gets the <see cref="ThePresenter.Net.NetCom.Client"/> with specified Uid.
		/// </summary>
		public Client this[Uid index]
		{
			get { return this._Clients[index]; }
		}

		/// <summary>
		/// Adds the specified client.
		/// </summary>
		/// <param name="C">The client.</param>
		/// <returns>Uid of inserted client.</returns>
		public Uid Add(Client C)
		{
			C.OnDisconnected += new Client.ClientDisconnectedEventHandler(Client_OnDisconnected);
			this._Clients.Add(C.LocalId, C);
			return C.LocalId;
		}

		/// <summary>
		/// Removes client with specifier Uid.
		/// </summary>
		/// <param name="C">Uid of client to remove.</param>
		public void Remove(Uid C)
		{
			if (!this._Clients.ContainsKey(C)) throw new ArgumentException("Could not find given Uid.");

			this._Clients.Remove(C);

			if (OnClientRemoved != null) OnClientRemoved(C);
		}

		/// <summary>
		/// Sends specified data to all clients.
		/// </summary>
		/// <param name="Data">The data.</param>
		public void Send(byte[] Data)
		{
			if (!(this.Count > 0)) throw new InvalidOperationException("Cannot send data. No clients available");

			foreach (Client C in this._Clients.Values)
			{
				C.Send(Data);
			}
		}

		/// <summary>
		/// Sends data to the specified target.
		/// </summary>
		/// <param name="Target">The target.</param>
		/// <param name="Data">The data.</param>
		public void Send(byte[] Data, Uid Target)
		{
			if (Target == null) throw new ArgumentException("Target cannot be null.");
			if (!this._Clients.ContainsKey(Target)) throw new ArgumentException("Target cannot be find.");

			this[Target].Send(Data);
		}

		/// <summary>
		/// Sends data to specified targets.
		/// </summary>
		/// <param name="Targets">The targets.</param>
		/// <param name="Data">The data.</param>
		public void Send(byte[] Data, IEnumerable<Uid> Targets)
		{
			if (Targets == null || Targets.Count() == 0) throw new ArgumentException("Targets cannot be null or empty.");

			IEnumerable<Client> Clients = ((Dictionary<Uid, Client>)this._Clients.Where(I => Targets.Contains(I.Key))).Values;

			if (Clients.Count() != Targets.Count()) throw new ArgumentException("Could not find target / targets.");

			foreach (Client C in Clients)
			{
				C.Send(Data);
			}
		}

		/// <summary>
		/// Gets array containing informations about all clients.
		/// </summary>
		/// <returns>Array of clients informations</returns>
		public ClientInfo[] ToArray()
		{
			List<ClientInfo> clients = new List<ClientInfo>();
			return _Clients.Select(I => I.Value.Info).ToArray();
		}

		/// <summary>
		/// Releases unmanaged and - optionally - managed resources
		/// </summary>
		internal void Dispose()
		{
			lock (this._Clients.Values)
			{
				foreach (Client C in this._Clients.Values)
				{
					C.Disconnect();
				}

				this._Clients = null;
			}
		}

		/// <summary>
		/// Handles OnDisconnected event from any of clients
		/// </summary>
		/// <param name="sender">The sender.</param>
		void Client_OnDisconnected(Uid sender)
		{
			if (OnClientDisconnected != null) OnClientDisconnected(sender);
		}

		#endregion

	}
}
